package cn.starrysky108.simpleerp.user.service.impl;

import cn.starrysky108.simpleerp.core.constant.RedisKeys;
import cn.starrysky108.simpleerp.core.constant.SecurityConstants;
import cn.starrysky108.simpleerp.core.util.entity.IpInfo;
import cn.starrysky108.simpleerp.core.enums.ResultCodeEnum;
import cn.starrysky108.simpleerp.core.exception.TokenException;
import cn.starrysky108.simpleerp.core.util.JwtUtil;
import cn.starrysky108.simpleerp.core.util.RedisUtil;
import cn.starrysky108.simpleerp.system.entity.SystemPermission;
import cn.starrysky108.simpleerp.system.entity.SystemRole;
import cn.starrysky108.simpleerp.user.entity.OnlineDeviceInfo;
import cn.starrysky108.simpleerp.user.entity.User;
import cn.starrysky108.simpleerp.user.mapper.UserMapper;
import cn.starrysky108.simpleerp.pub.response.TokenResponse;
import cn.starrysky108.simpleerp.user.service.OnlineDeviceInfoService;
import cn.starrysky108.simpleerp.system.service.SystemDepartmentService;
import cn.starrysky108.simpleerp.user.service.UserService;
import cn.starrysky108.simpleerp.user.vo.UserVo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestHeader;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author Carl Lee
 */
@Service
@CacheConfig(cacheNames = "userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    private final OnlineDeviceInfoService deviceInfoService;
    private final SystemDepartmentService departmentService;

    @Override
    @Cacheable(key = "'user:info:'+#username")
    public User getUser(String username) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        return getOne(queryWrapper);
    }

    @Override
    public UserVo getUserVo(Long id) {
        UserVo userVo = new UserVo();
        User user = getById(id);
        BeanUtils.copyProperties(user, userVo);
        userVo.setRoles(listRoleByUserId(id));
        if (!ObjectUtils.isEmpty(user.getDepartmentId())) {
            userVo.setDepartment(departmentService.getById(user.getDepartmentId()));
        }
        return userVo;
    }

    @Override
    public List<SystemRole> listRoleByUserId(Long userId) {
        return baseMapper.listRoleByUserId(userId);
    }

    @Override
    public List<SystemPermission> listPermissionByUserId(Long userId) {
        return baseMapper.listPermissionByUserId(userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public TokenResponse getToken(String refreshToken, HttpServletRequest request, HttpServletResponse response) {
        if (!StringUtils.hasText(refreshToken)) {
            throw new TokenException(refreshToken, ResultCodeEnum.TOKEN_ILLEGAL);
        }
        Long userId = JwtUtil.getUserId(refreshToken);
        String username = JwtUtil.getUserName(refreshToken);
        //验证refresh_token的合法性
        JwtUtil.verify(refreshToken, response);
        if (RedisUtil.hasKey(RedisKeys.TOKEN_DENNY + refreshToken)) {
            throw new TokenException(refreshToken, ResultCodeEnum.TOKEN_ILLEGAL);
        }
        String accessToken = JwtUtil.getHeaderToken(request);
        TokenResponse tokenResponse = TokenResponse.builder()
                .refreshToken(JwtUtil.getRefreshToken(username, userId))
                .accessToken(JwtUtil.getAccessToken(username, userId))
                .build();
        //将旧token放入token黑名单中，使其失效
        RedisUtil.addToken(RedisKeys.TOKEN_DENNY, refreshToken);
        //将旧的未过期的access_token加入黑名单
        if (StringUtils.hasText(accessToken)) {
            RedisUtil.addToken(RedisKeys.TOKEN_DENNY, accessToken);
        }
        //使用新token进行登录
        // 删除旧token的登录信息
        RedisUtil.hDel(RedisKeys.ONLINE_USER + userId, refreshToken);
        return tokenResponse;
    }

    @Override
    public TokenResponse register(User user, HttpServletResponse response) {

        assert user != null;
        //对原始密码进行加密
        String pwd = JwtUtil.encryptPassword(user.getPassword());
        user.setPassword(pwd);
        //保存user,遇到错误时直接跑出
        this.save(user);
        TokenResponse tokenResponse = TokenResponse.builder()
                .refreshToken(JwtUtil.getRefreshToken(user.getUsername(), user.getId()))
                .accessToken(JwtUtil.getAccessToken(user.getUsername(), user.getId()))
                .user(getUserVo(user.getId()))
                .build();
        // 将签发的 JWT token 设置到 HttpServletResponse 的 Header 中
        response.setHeader(SecurityConstants.AUTH_HEADER, tokenResponse.getAccessToken());
        //写入redis
        RedisUtil.sAdd(RedisKeys.ONLINE_USER + user.getId(), tokenResponse.getRefreshToken());
        return tokenResponse;
    }

    @Override
    public void logout(@RequestHeader String accessToken, String refreshToken) {
        //将token加入黑名单
        RedisUtil.addToken(RedisKeys.TOKEN_DENNY, accessToken);
        RedisUtil.addToken(RedisKeys.TOKEN_DENNY, refreshToken);
        //删除在线设备信息
        RedisUtil.hDel(RedisKeys.ONLINE_USER + JwtUtil.getUserId(refreshToken), refreshToken);

    }

    @Override
    public Map<String, OnlineDeviceInfo> listOnlineDevice(Long userId) {
        Map<String, OnlineDeviceInfo> map = RedisUtil.hGet(RedisKeys.ONLINE_USER + userId, OnlineDeviceInfo.class);

        map.forEach((key, value) -> {
            value.initIpInfo();
            try {
                IpInfo ipInfo = deviceInfoService.getIpInfo(value.getHost()).get();
                value.setIpInfo(ipInfo);
                map.put(key, value);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        });
        return map;
    }

    public UserServiceImpl(OnlineDeviceInfoService deviceInfoService, SystemDepartmentService departmentService) {
        this.deviceInfoService = deviceInfoService;
        this.departmentService = departmentService;
    }
}
